package com.hyperf.core.lock.aop;

import cn.hutool.core.util.StrUtil;
import com.hyperf.core.lock.LockInfo;
import com.hyperf.core.lock.LockTemplate;
import com.hyperf.core.lock.annotation.DistributedLock;
import com.hyperf.core.lock.exception.LockException;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;

/**
 * @author lizhaoyang
 */
public class LockInterceptor implements MethodInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LockInterceptor.class);

    private LockTemplate lockTemplate;

    public LockInterceptor() {
    }

    public LockInterceptor(LockTemplate lockTemplate) {
        this.lockTemplate = lockTemplate;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        LockInfo lockInfo = null;

        try {
            DistributedLock distributedLock = invocation.getMethod().getAnnotation(DistributedLock.class);
            String lockKey = buildLockKey(invocation, distributedLock);
            lockInfo = lockTemplate.lock(lockKey, distributedLock.expire(), distributedLock.acquireTimeout());
            logger.info("LockInterceptor lock: {}", lockInfo);
            if (lockInfo != null) {
                return invocation.proceed();
            }
        } finally {
            if (lockInfo != null) {
                boolean releaseLock = lockTemplate.releaseLock(lockInfo);
                if (!releaseLock) {
                    logger.error("LockInterceptor release lock fail, {}", lockInfo);
                } else {
                    logger.info("LockInterceptor release lock success: {}", lockInfo);
                }
            }
        }

        return null;
    }

    private String buildLockKey(MethodInvocation invocation, DistributedLock distributedLock) {
        String lockKey = distributedLock.key();
        if (!StrUtil.isEmpty(lockKey)) {
            try {
                return parseKey(lockKey, invocation.getMethod(), invocation.getArguments());
            } catch (Exception exception) {
                return String.format("%s::%s", invocation.getMethod().getDeclaringClass().getName(), invocation.getMethod().getName());
            }
        }

        return lockKey;
    }

    private String parseKey(String key, Method method, Object[] args) {
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);

        if (paraNameArr == null) {
            throw new LockException("paraNameArr is null");
        }

        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        return parser.parseExpression(key).getValue(context, String.class);
    }

    public LockTemplate getLockTemplate() {
        return lockTemplate;
    }

    public void setLockTemplate(LockTemplate lockTemplate) {
        this.lockTemplate = lockTemplate;
    }
}
